// This code is derived from jcifs smb client library <jcifs at samba dot org>
// Ported by J. Arturo <webmaster at komodosoft dot net>
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
using System;
using SharpCifs.Util.Sharpen;

namespace SharpCifs.Smb
{
    class SmbTree
    {
        private static int _treeConnCounter;

        internal int ConnectionState;

        internal int Tid;

        internal string Share;

        internal string Service = "?????";

        internal string Service0;

        internal SmbSession Session;

        internal bool InDfs;

        internal bool InDomainDfs;

        internal int TreeNum;

        internal SmbTree(SmbSession session, string share, string service)
        {
            // used by SmbFile.isOpen
            this.Session = session;
            this.Share = share.ToUpper();
            if (service != null && service.StartsWith("??") == false)
            {
                this.Service = service;
            }
            Service0 = this.Service;
            ConnectionState = 0;
        }

        internal virtual bool Matches(string share, string service)
        {
            return Runtime.EqualsIgnoreCase(this.Share, share)
                    && (service == null
                        || service.StartsWith("??")
                        || Runtime.EqualsIgnoreCase(this.Service, service));
        }

        public override bool Equals(object obj)
        {
            if (obj is SmbTree)
            {
                SmbTree tree = (SmbTree)obj;
                return Matches(tree.Share, tree.Service);
            }
            return false;
        }

        /// <exception cref="SharpCifs.Smb.SmbException"></exception>
        internal virtual void Send(ServerMessageBlock request, ServerMessageBlock response)
        {
            lock (Session.Transport())
            {
                if (response != null)
                {
                    response.Received = false;
                }
                TreeConnect(request, response);
                if (request == null || (response != null && response.Received))
                {
                    return;
                }
                if (Service.Equals("A:") == false)
                {
                    switch (request.Command)
                    {
                        case ServerMessageBlock.SmbComOpenAndx:
                        case ServerMessageBlock.SmbComNtCreateAndx:
                        case ServerMessageBlock.SmbComReadAndx:
                        case ServerMessageBlock.SmbComWriteAndx:
                        case ServerMessageBlock.SmbComClose:
                        case ServerMessageBlock.SmbComTreeDisconnect:
                            {
                                break;
                            }

                        case ServerMessageBlock.SmbComTransaction:
                        case ServerMessageBlock.SmbComTransaction2:
                            {
                                switch (((SmbComTransaction)request).SubCommand
                                         & unchecked(0xFF))
                                {
                                    case SmbComTransaction.NetShareEnum:
                                    case SmbComTransaction.NetServerEnum2:
                                    case SmbComTransaction.NetServerEnum3:
                                    case SmbComTransaction.TransPeekNamedPipe:
                                    case SmbComTransaction.TransWaitNamedPipe:
                                    case SmbComTransaction.TransCallNamedPipe:
                                    case SmbComTransaction.TransTransactNamedPipe:
                                    case SmbComTransaction.Trans2GetDfsReferral:
                                        {
                                            break;
                                        }

                                    default:
                                        {
                                            throw new SmbException(
                                                "Invalid operation for " + Service + " service");
                                        }
                                }
                                break;
                            }

                        default:
                            {
                                throw new SmbException(
                                    "Invalid operation for " + Service + " service" + request);
                            }
                    }
                }
                request.Tid = Tid;
                if (InDfs
                    && !Service.Equals("IPC")
                    && !string.IsNullOrEmpty(request.Path))
                {
                    request.Flags2 = SmbConstants.Flags2ResolvePathsInDfs;
                    request.Path = '\\' + Session.Transport().TconHostName
                                    + '\\' + Share + request.Path;
                }
                try
                {
                    Session.Send(request, response);
                }
                catch (SmbException se)
                {
                    if (se.GetNtStatus() == NtStatus.NtStatusNetworkNameDeleted)
                    {
                        TreeDisconnect(true);
                    }
                    throw;
                }
            }
        }

        /// <exception cref="SharpCifs.Smb.SmbException"></exception>
        internal virtual void TreeConnect(ServerMessageBlock andx,
                                          ServerMessageBlock andxResponse)
        {
            lock (Session.Transport())
            {
                string unc;
                while (ConnectionState != 0)
                {
                    if (ConnectionState == 2 || ConnectionState == 3)
                    {
                        // connected or disconnecting
                        return;
                    }
                    try
                    {
                        Runtime.Wait(Session.transport);
                    }
                    catch (Exception ie)
                    {
                        throw new SmbException(ie.Message, ie);
                    }
                }
                ConnectionState = 1;
                // trying ...
                try
                {
                    Session.transport.Connect();
                    unc = "\\\\" + Session.transport.TconHostName + '\\' + Share;
                    Service = Service0;
                    if (Session.transport.Log.Level >= 4)
                    {
                        Session.transport.Log.WriteLine(
                            "treeConnect: unc=" + unc
                            + ",service=" + Service);
                    }
                    SmbComTreeConnectAndXResponse response
                        = new SmbComTreeConnectAndXResponse(andxResponse);
                    SmbComTreeConnectAndX request
                        = new SmbComTreeConnectAndX(Session, unc, Service, andx);
                    Session.Send(request, response);
                    Tid = response.Tid;
                    Service = response.Service;
                    InDfs = response.ShareIsInDfs;
                    TreeNum = _treeConnCounter++;
                    ConnectionState = 2;
                }
                catch (SmbException se)
                {
                    // connected
                    TreeDisconnect(true);
                    ConnectionState = 0;
                    throw;
                }
            }
        }

        internal virtual void TreeDisconnect(bool inError)
        {
            lock (Session.Transport())
            {
                if (ConnectionState != 2)
                {
                    // not-connected
                    return;
                }
                ConnectionState = 3;
                // disconnecting
                if (!inError && Tid != 0)
                {
                    try
                    {
                        Send(new SmbComTreeDisconnect(), null);
                    }
                    catch (SmbException se)
                    {
                        if (Session.transport.Log.Level > 1)
                        {
                            Runtime.PrintStackTrace(se, Session.transport.Log);
                        }
                    }
                }
                InDfs = false;
                InDomainDfs = false;
                ConnectionState = 0;
                Runtime.NotifyAll(Session.transport);
            }
        }

        public override string ToString()
        {
            return "SmbTree[share=" + Share
                        + ",service=" + Service
                        + ",tid=" + Tid
                        + ",inDfs=" + InDfs
                        + ",inDomainDfs=" + InDomainDfs
                        + ",connectionState=" + ConnectionState + "]";
        }
    }
}
